package lz.controls
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.ui.Keyboard;
	import flash.utils.Dictionary;

	import lz.controls.ScrollBar;
	import lz.controls.ScrollBarDirection;
	import lz.controls.ScrollPolicy;
	import lz.controls.SelectableList;
	import lz.controls.core.InvalidationType;
	import lz.controls.core.UIComponent;
	import lz.controls.data.DataProvider;
	import lz.controls.data.TileListCollectionItem;
	import lz.controls.events.DataChangeEvent;
	import lz.controls.events.DataChangeType;
	import lz.controls.events.ListEvent;
	import lz.controls.events.ScrollEvent;
	import lz.controls.listClasses.CellRenderer;
	import lz.controls.listClasses.ICellRenderer;
	import lz.controls.listClasses.ImageCell;
	import lz.controls.listClasses.ListData;
	import lz.controls.listClasses.TileListData;
	import lz.controls.managers.IFocusManagerComponent;
	/**
	 * TileList 类提供呈行和列分布的网格，通常用来以“平铺”格式设置并显示图像。 
	 * 用于此组件的默认单元格渲染器为 ImageCell 类。 
	 * ImageCell 单元格渲染器将显示缩略图图像和一个单行标签。 
	 * 若要呈现 TileList 组件中基于列表的单元格，请使用 CellRenderer 类。
	 * 若要修改分隔图像单元格边框的填充，可以通过全局方式设置 imagePadding 样式，或者在 ImageCell 类上设置该样式。 
	 * 与其他单元格样式一样，imagePadding 样式无法在 TileList 组件实例上进行设置。
	 */
	public class TileList extends SelectableList implements IFocusManagerComponent
	{
		/**
		 * 行高
		 */		
		protected var _rowHeight:Number=50;
		/**
		 * 列宽
		 */
		protected var _columnWidth:Number=50;
		/**
		 *滚动方向（默认水平）
		 */
		protected var _scrollDirection:String=ScrollBarDirection.HORIZONTAL;
		/**
		 * 滚动策略（默认自动）
		 */
		protected var _scrollPolicy:String=ScrollPolicy.AUTO;
		/**
		 * 条目渲染器
		 */
		protected var _cellRenderer:Object;
		/**
		 * 旧长度
		 */
		protected var oldLength:uint=0;
		/**
		 * 条目文字
		 */
		protected var _labelField:String="label";
		/**
		 * 该函数显示项的多个字段，用于提供拼贴的标签文本。
		 */
		protected var _labelFunction:Function;
		/**
		 * 用于提供项的图标的项字段
		 */
		protected var _iconField:String="icon";
		/**
		 * 用于获得项的图标的函数
		 */
		protected var _iconFunction:Function;
		/**
		 * 用于提供拼贴的源路径的项字段
		 */
		protected var _sourceField:String="source";
		/**
		 * 用于获得拼贴的源路径的函数
		 */
		protected var _sourceFunction:Function;
		/**
		 * 列表中至少部分可见的行的行数
		 */
		protected var __rowCount:uint=0;
		/**
		 * 应用于列表中每一行的高度，以像素为单位
		 */
		protected var __columnCount:uint=0;
		/**
		 * 默认样式
		 */
		private static var defaultStyles:Object={cellRenderer: ImageCell, focusRectSkin: null, focusRectPadding: null, skin: "TileList_skin"};
		/**
		 * 检索当前组件的默认样式映射
		 */
		public static function getStyleDefinition():Object
		{
			return mergeStyles(defaultStyles, SelectableList.getStyleDefinition(), ScrollBar.getStyleDefinition());
		}

		/**
		 *  @private
		 *  Method for creating the Accessibility class.
		 *  This method is called from UIComponent.
		 */
		public static var createAccessibilityImplementation:Function;


		/**
		 * Creates a new List component instance.
		 */
		public function TileList()
		{
			super();
		}

		/**
		 */
		override public function get dataProvider():DataProvider
		{
			return super.dataProvider;
		}

		/**
		 * @private (setter)
		 */
		override public function set dataProvider(value:DataProvider):void
		{
			super.dataProvider=value;
		}

		/**
		 * Gets or sets a field in each item that contains a label for each tile.
		 */
		public function get labelField():String
		{
			return _labelField;
		}

		/**
		 * @private (setter)
		 */
		public function set labelField(value:String):void
		{
			if (value == _labelField)
			{
				return;
			}
			_labelField=value;
			invalidate(InvalidationType.DATA);
		}

		/**
		 * Gets a function that indicates the fields of an item that provide the label text for a tile.
		 *
		 * <p><strong>Note:</strong> The <code>labelField</code> is not used if
		 * the <code>labelFunction</code> property is set to a callback function.</p>
		 *
		 * @default null
		 */
		public function get labelFunction():Function
		{
			return _labelFunction;
		}

		/**
		 * @private (setter)
		 */
		public function set labelFunction(value:Function):void
		{
			if (_labelFunction == value)
			{
				return;
			}
			_labelFunction=value;
			invalidate(InvalidationType.DATA);
		}

		/**
		 * Gets or sets the item field that provides the icon for the item.
		 *
		 * <p><strong>Note:</strong> The <code>iconField</code> is not used
		 * if the <code>iconFunction</code> property is set to a callback function.</p>
		 *
		 * <p>Icons can be classes or they can be symbols from the library that have a class name.</p>
		 *
		 * @default null
		 */
		public function get iconField():String
		{
			return _iconField;
		}

		/**
		 * @private (setter)
		 */
		public function set iconField(value:String):void
		{
			if (value == _iconField)
			{
				return;
			}
			_iconField=value;
			invalidate(InvalidationType.DATA);
		}

		/**
		 * Gets or sets the function to be used to obtain the icon for the item.
		 *
		 * <p><strong>Note:</strong> The <code>iconField</code> is not used if the
		 * <code>iconFunction</code> property is set to a callback function.</p>
		 *
		 * <p>Icons can be classes, or they can be library items that have class names.</p>
		 *
		 * @default null
		 */
		public function get iconFunction():Function
		{
			return _iconFunction;
		}

		/**
		 * @private (setter)
		 */
		public function set iconFunction(value:Function):void
		{
			if (_iconFunction == value)
			{
				return;
			}
			_iconFunction=value;
			invalidate(InvalidationType.DATA);
		}

		/**
		 * Gets or sets the item field that provides the source path for a tile.
		 *
		 * <p><strong>Note:</strong> The <code>sourceField</code> is not used if the
		 * <code>sourceFunction</code> property is set to a callback function.</p>
		 *
		 * @default "source"
		 */
		public function get sourceField():String
		{
			return _sourceField;
		}

		/**
		 * @private (setter)
		 */
		public function set sourceField(value:String):void
		{
			_sourceField=value;
			invalidate(InvalidationType.DATA);
		}

		/**
		 * Gets or sets the function to be used to obtain the source path for a tile.
		 *
		 * <p><strong>Note:</strong> The <code>sourceField</code> is not used if the
		 * <code>sourceFunction</code> property is set to a callback function.</p>
		 *
		 * @default null
		 */
		public function get sourceFunction():Function
		{
			return _sourceFunction;
		}

		/**
		 * @private (setter)
		 */
		public function set sourceFunction(value:Function):void
		{
			_sourceFunction=value;
			invalidate(InvalidationType.DATA);
		}


		[Inspectable(defaultValue=0, type="Number")]
		/**
		 * Gets or sets the number of rows that are at least partially visible
		 * in the list.
		 *
		 * <p>Setting the <code>rowCount</code> property changes the height of the
		 * list, but the TileList component does not maintain this value. It
		 * is important to set the <code>rowCount</code> value <em>after</em> setting the
		 * <code>dataProvider</code> and <code>rowHeight</code> values. The only
		 * exception is if the <code>rowCount</code> is set with the Property
		 * inspector; in this case, the property is maintained until the component
		 * is first drawn.</p>
		 *
		 * @default 0
		 */
		override public function get rowCount():uint
		{
			var pad:Number=Number(getStyleValue("contentPadding"));
			var cols:uint=Math.max(1, (_width - 2 * pad) / _columnWidth << 0);
			var rows:uint=Math.max(1, (_height - 2 * pad) / _rowHeight << 0);
			if (_scrollDirection == ScrollBarDirection.HORIZONTAL)
			{
				if (_scrollPolicy == ScrollPolicy.ON || (_scrollPolicy == ScrollPolicy.AUTO && length > cols * rows))
				{
					// account for horizontal scrollbar:
					rows=Math.max(1, (_height - 2 * pad - 15) / _rowHeight << 0);
				}
					// else use the default rows value from above.
			}
			else
			{
				// we might have a partial row visible:
				rows=Math.max(1, Math.ceil((_height - 2 * pad) / _rowHeight));
			}
			return rows;
		}

		/**
		 * @private (setter)
		 */
		public function set rowCount(value:uint):void
		{
			if (value == 0)
			{
				return;
			}
			if (componentInspectorSetting)
			{
				__rowCount=value;
				return;
			}
			__rowCount=0;

			var pad:Number=Number(getStyleValue("contentPadding"));
			var showScroll:*=(Math.ceil(length / value) > (width / columnWidth) >> 0 && _scrollPolicy == ScrollPolicy.AUTO) || _scrollPolicy == ScrollPolicy.ON;
			height=rowHeight * value + 2 * pad + ((_scrollDirection == ScrollBarDirection.HORIZONTAL && showScroll) ? ScrollBar.WIDTH : 0);
		}

		[Inspectable(defaultValue=50)]
		/**
		 * Gets or sets the height that is applied to each row in the list, in pixels.
		 *
		 * @default 50
		 */
		public function get rowHeight():Number
		{
			return _rowHeight;
		}

		/**
		 * @private (setter)
		 */
		public function set rowHeight(value:Number):void
		{
			if (_rowHeight == value)
			{
				return;
			}
			_rowHeight=value;
			invalidate(InvalidationType.SIZE);
		}

		[Inspectable(defaultValue=0, type="Number")]
		/**
		 * Gets or sets the number of columns that are at least partially visible in the
		 * list. Setting the <code>columnCount</code> property changes the width of the list,
		 * but the TileList component does not maintain this value. It is important to set the
		 * <code>columnCount</code> value <em>after</em> setting the <code>dataProvider</code>
		 * and <code>rowHeight</code> values. The only exception is if the <code>rowCount</code>
		 * is set with the Property inspector; in this case, the property is maintained until the
		 * component is first drawn.
		 *
		 * @default 0
		 */
		public function get columnCount():uint
		{
			var pad:Number=Number(getStyleValue("contentPadding"));
			var cols:uint=Math.max(1, (_width - 2 * pad) / _columnWidth << 0);
			var rows:uint=Math.max(1, (_height - 2 * pad) / _rowHeight << 0);
			if (_scrollDirection != ScrollBarDirection.HORIZONTAL)
			{
				if (_scrollPolicy == ScrollPolicy.ON || (_scrollPolicy == ScrollPolicy.AUTO && length > cols * rows))
				{
					// account for vertical scrollbar:
					cols=Math.max(1, (_width - 2 * pad - 15) / _columnWidth << 0);
				}
					// else we just use the default cols value from above.
			}
			else
			{
				// we might have a partial column visible:
				cols=Math.max(1, Math.ceil((_width - 2 * pad) / _columnWidth));
			}
			return cols;
		}

		/**
		 * @private (setter)
		 */
		public function set columnCount(value:uint):void
		{
			if (value == 0)
			{
				return;
			}
			if (componentInspectorSetting)
			{
				__columnCount=value;
				return;
			}
			__columnCount=0;

			var pad:Number=Number(getStyleValue("contentPadding"));
			var showScroll:Boolean=(Math.ceil(length / value) > (height / rowHeight) >> 0 && _scrollPolicy == ScrollPolicy.AUTO) || _scrollPolicy == ScrollPolicy.ON;
			width=columnWidth * value + 2 * pad + (_scrollDirection == ScrollBarDirection.VERTICAL && showScroll ? 15 : 0);
		}

		[Inspectable(defaultValue=50)]
		/**
		 * Gets or sets the width that is applied to a column in the list, in pixels.
		 *
		 * @default 50
		 */
		public function get columnWidth():Number
		{
			return _columnWidth;
		}

		/**
		 * @private (setter)
		 */
		public function set columnWidth(value:Number):void
		{
			if (_columnWidth == value)
			{
				return;
			}
			_columnWidth=value;
			invalidate(InvalidationType.SIZE);
		}

		/**
		 * Gets the width of the content area, in pixels. This value is the component width
		 * minus the combined width of the <code>contentPadding</code> value and vertical scroll bar,
		 * if the vertical scroll bar is visible.
		 */
		public function get innerWidth():Number
		{
			drawNow();
			var contentPadding:Number=getStyleValue("contentPadding") as Number;
			return width - contentPadding * 2 - (_verticalScrollBar.visible ? _verticalScrollBar.width : 0);
		}

		/**
		 * Gets the height of the content area, in pixels. This value is the component height
		 * minus the combined height of the <code>contentPadding</code> value and horizontal
		 * scroll bar height, if the horizontal scroll bar is visible.
		 */
		public function get innerHeight():Number
		{
			drawNow();
			var contentPadding:Number=getStyleValue("contentPadding") as Number;
			return height - contentPadding * 2 - (_horizontalScrollBar.visible ? _horizontalScrollBar.height : 0);
		}

		[Inspectable(enumeration="horizontal,vertical", defaultValue="horizontal")]
		/**
		 * Gets or sets a value that indicates whether the TileList component scrolls
		 * horizontally or vertically. A value of <code>ScrollBarDirection.HORIZONTAL</code>
		 * indicates that the TileList component scrolls horizontally; a value of
		 * <code>ScrollBarDirection.VERTICAL</code> indicates that the TileList component scrolls vertically.
		 */
		public function get direction():String
		{
			return _scrollDirection;
		}

		/**
		 * @private (setter)
		 */
		public function set direction(value:String):void
		{
			if (_scrollDirection == value)
			{
				return;
			}
			_scrollDirection=value;
			invalidate(InvalidationType.SIZE);
		}

		[Inspectable(enumeration="auto,on,off", defaultValue="auto")]
		/**
		 * Gets or sets the scroll policy for the TileList component. This
		 * value is used to specify the scroll policy for the scroll bar that
		 * is set by the <code>direction</code> property.
		 *
		 * <p><strong>Note:</strong> The TileList component supports scrolling only in
		 * one direction. Tiles are adjusted to fit into the viewable area of
		 * the component, so that tiles are hidden in only one direction.</p>
		 *
		 * <p>The TileList component resizes to fit tiles only when the user
		 * manually sets the size or when the user sets the <code>rowCount</code>
		 * or <code>columnCount</code> properties.</p>
		 *
		 * <p>When this value is set to <code>ScrollPolicy.AUTO</code>, the
		 * scroll bar is visible only when the TileList component must scroll
		 * to show all the items.</p>
		 *
		 * @default ScrollPolicy.AUTO
		 */
		public function get scrollPolicy():String
		{
			return _scrollPolicy;
		}

		/**
		 * @private (setter)
		 */
		public function set scrollPolicy(value:String):void
		{
			if (!componentInspectorSetting && _scrollPolicy == value)
			{
				return;
			}
			_scrollPolicy=value;
			if (direction == ScrollBarDirection.HORIZONTAL)
			{
				_horizontalScrollPolicy=value;
				_verticalScrollPolicy=ScrollPolicy.OFF;
			}
			else
			{
				_verticalScrollPolicy=value;
				_horizontalScrollPolicy=ScrollPolicy.OFF;
			}
			invalidate(InvalidationType.SIZE);
		}

		/**
		 */
		override public function scrollToIndex(newCaretIndex:int):void
		{
			drawNow(); // Force validation.

			var totalCols:uint=Math.max(1, (contentWidth / _columnWidth << 0));
			if (_scrollDirection == ScrollBarDirection.VERTICAL)
			{
				if (rowHeight > availableHeight)
				{
					return; // nothing: don't scroll if the item is bigger than the viewable area)
				}
				var itemY:Number=(newCaretIndex / totalCols >> 0) * rowHeight;
				if (itemY < verticalScrollPosition)
				{
					verticalScrollPosition=itemY;
				}
				else if (itemY > verticalScrollPosition + availableHeight - rowHeight)
				{
					verticalScrollPosition=itemY + rowHeight - availableHeight;
				}
			}
			else
			{
				if (columnWidth > availableWidth)
				{
					return;
				}
				var itemX:Number=newCaretIndex % totalCols * columnWidth;
				if (itemX < horizontalScrollPosition)
				{
					horizontalScrollPosition=itemX;
				}
				else if (itemX > horizontalScrollPosition + availableWidth - columnWidth)
				{
					horizontalScrollPosition=itemX + columnWidth - availableWidth;
				}
			}
		}


		/**
		 * Retrieves the string that the renderer displays for a given data object
		 * based on the <code>labelField</code> and <code>labelFunction</code> properties.
		 *
		 * @param item The Object to be rendered.
		 *
		 * @return The string to be displayed based on the data.
		 */
		override public function itemToLabel(item:Object):String
		{
			if (_labelFunction != null)
			{
				return String(_labelFunction(item));
			}
			else
			{
				if (item[_labelField] == null)
				{
					return "";
				}
				return String(item[_labelField]);
			}
		}


		// Hide these from the Property inspector and from users.
		/**
		 * @private (hidden)
		 */
		override public function get verticalScrollPolicy():String
		{
			return null;
		}

		/**
		 * @private (setter)
		 */
		override public function set verticalScrollPolicy(value:String):void
		{
		}

		/**
		 * @private (hidden)
		 */
		override public function get horizontalScrollPolicy():String
		{
			return null;
		}

		/**
		 * @private (setter)
		 */
		override public function set horizontalScrollPolicy(value:String):void
		{
		}

		/**
		 * Gets the maximum horizontal scroll position for the current content, in pixels.
		 */
		override public function get maxHorizontalScrollPosition():Number
		{
			drawNow();
			return _maxHorizontalScrollPosition;
		}

		/**
		 * @private (setter)
		 */
		override public function set maxHorizontalScrollPosition(value:Number):void
		{
		}

		/**
		 * @private (protected)
		 */
		override protected function configUI():void
		{
			super.configUI();
			_horizontalScrollPolicy=scrollPolicy;
			_verticalScrollPolicy=ScrollPolicy.OFF;
		}

		/**
		 * @private (protected)
		 */
		override protected function setHorizontalScrollPosition(scroll:Number, fireEvent:Boolean=false):void
		{
			invalidate(InvalidationType.SCROLL);
			super.setHorizontalScrollPosition(scroll, true);
		}

		/**
		 * @private (protected)
		 */
		override protected function setVerticalScrollPosition(scroll:Number, fireEvent:Boolean=false):void
		{
			invalidate(InvalidationType.SCROLL);
			super.setVerticalScrollPosition(scroll, true);
		}

		/**
		 * @private (protected)
		 */
		override protected function draw():void
		{
			// We need to draw the row and column count that are set by component parameters on the first draw.
			if (direction == ScrollBarDirection.VERTICAL)
			{
				if (__rowCount > 0)
				{
					rowCount=__rowCount;
				}
				if (__columnCount > 0)
				{
					columnCount=__columnCount;
				}
			}
			else
			{
				if (__columnCount > 0)
				{
					columnCount=__columnCount;
				}
				if (__rowCount > 0)
				{
					rowCount=__rowCount;
				}
			}

			var lengthChanged:Boolean=(oldLength != length);
			oldLength=length;

			if (isInvalid(InvalidationType.STYLES))
			{
				setStyles();
				drawBackground();
				// drawLayout is expensive, so only do it if padding has changed:
				if (contentPadding != getStyleValue("contentPadding"))
				{
					invalidate(InvalidationType.SIZE, false);
				}
				// redrawing all the cell renderers is even more expensive, so we really only want to do it if necessary:
				if (_cellRenderer != getStyleValue("cellRenderer"))
				{
					// remove all the existing renderers:
					_invalidateList();
					_cellRenderer=getStyleValue("cellRenderer");
				}
			}
			if (isInvalid(InvalidationType.SIZE, InvalidationType.STATE) || lengthChanged)
			{
				drawLayout();
			}

			// Apply updatedStyles
			if (isInvalid(InvalidationType.RENDERER_STYLES))
			{
				updateRendererStyles();
			}

			if (isInvalid(InvalidationType.STYLES, InvalidationType.SIZE, InvalidationType.DATA, InvalidationType.SCROLL, InvalidationType.SELECTED))
			{
				drawList();
				_maxHorizontalScrollPosition=Math.max(0, contentWidth - availableWidth);
			}


			// Call drawNow on nested components to get around problems with nested render events:
			updateChildren();

			// not calling super.draw, because we're handling everything here. Instead we'll just call validate();
			validate();
		}

		/**
		 * @private (protected)
		 */
		override protected function drawLayout():void
		{
			// figure out our scrolling situation:
			_horizontalScrollPolicy=(_scrollDirection == ScrollBarDirection.HORIZONTAL) ? _scrollPolicy : ScrollPolicy.OFF;
			_verticalScrollPolicy=(_scrollDirection != ScrollBarDirection.HORIZONTAL) ? _scrollPolicy : ScrollPolicy.OFF;
			if (_scrollDirection == ScrollBarDirection.HORIZONTAL)
			{
				var rows:uint=rowCount;
				contentHeight=rows * _rowHeight;
				contentWidth=_columnWidth * Math.ceil(length / rows);
			}
			else
			{
				var cols:uint=columnCount;
				contentWidth=cols * _columnWidth;
				contentHeight=_rowHeight * Math.ceil(length / cols);
			}
			// hand off drawing the layout to BaseScrollPane:
			super.drawLayout();
		}

		/**
		 * @private (protected)
		 */
		override protected function drawList():void
		{
			// these vars get reused in different loops:
			var i:uint;
			var itemIndex:uint;
			var item:Object;
			var renderer:ICellRenderer;
			var rows:uint=rowCount;
			var cols:uint=columnCount;
			var colW:Number=columnWidth;
			var rowH:Number=rowHeight;
			var baseCol:Number=0;
			var baseRow:Number=0;
			var col:uint;
			var row:uint;

			listHolder.x=listHolder.y=contentPadding;

			// set horizontal scroll:
			contentScrollRect=listHolder.scrollRect;
			contentScrollRect.x=Math.floor(_horizontalScrollPosition) % colW;

			// set pixel scroll:
			contentScrollRect.y=Math.floor(_verticalScrollPosition) % rowH;
			listHolder.scrollRect=contentScrollRect;

			listHolder.cacheAsBitmap=useBitmapScrolling;

			// figure out what we have to render, and where:
			var items:Array=[];
			if (_scrollDirection == ScrollBarDirection.HORIZONTAL)
			{
				// horizontal scrolling is trickier if we want to keep tiles going left to right, then top to bottom.
				// we can use availableWidth / availableHeight from BaseScrollPane here, because we've just called drawLayout, so we know they are accurate.
				var fullCols:uint=availableWidth / colW << 0;
				var rowLength:uint=Math.max(fullCols, Math.ceil(length / rows));
				baseCol=_horizontalScrollPosition / colW << 0;
				cols=Math.max(fullCols, Math.min(rowLength - baseCol, cols + 1)); //(horizontalScrollBar.visible ? 1 : -1))); // need to draw an extra two cols for scrolling.
				//rowLength = Math.max(cols-(horizontalScrollBar.visible ? -1 : 0),rowLength);
				for (row=0; row < rows; row++)
				{
					for (col=0; col < cols; col++)
					{
						itemIndex=row * rowLength + baseCol + col;
						if (itemIndex >= length)
						{
							break;
						}
						items.push(itemIndex);
					}
				}
			}
			else
			{
				rows++; // need to draw an extra row for scrolling.
				baseRow=_verticalScrollPosition / rowH << 0;
				var startIndex:uint=Math.floor(baseRow * cols);
				var endIndex:uint=Math.min(length, startIndex + rows * cols);
				for (i=startIndex; i < endIndex; i++)
				{
					items.push(i);
				}
			}

			// create a dictionary for looking up the new "displayed" items:
			var itemHash:Dictionary=renderedItems=new Dictionary(true);
			for each (itemIndex in items)
			{
				itemHash[_dataProvider.getItemAt(itemIndex)]=true;
			}

			// find cell renderers that are still active, and make those that aren't active available:
			var itemToRendererHash:Dictionary=new Dictionary(true);
			while (activeCellRenderers.length > 0)
			{
				renderer=activeCellRenderers.pop();
				item=renderer.data;
				if (itemHash[item] == null || invalidItems[item] == true)
				{
					availableCellRenderers.push(renderer);
				}
				else
				{
					itemToRendererHash[item]=renderer;
					// prevent problems with duplicate objects:
					invalidItems[item]=true;
				}
				list.removeChild(renderer as DisplayObject);
			}
			invalidItems=new Dictionary(true);

			i=0; // count of items placed.
			// draw cell renderers:
			for each (itemIndex in items)
			{
				col=i % cols;
				row=i / cols << 0;

				var reused:Boolean=false;
				item=_dataProvider.getItemAt(itemIndex);
				if (itemToRendererHash[item] != null)
				{
					// existing renderer for this item we can reuse:
					reused=true;
					renderer=itemToRendererHash[item];
					delete(itemToRendererHash[item]);
				}
				else if (availableCellRenderers.length > 0)
				{
					// recycle an old renderer:
					renderer=availableCellRenderers.pop() as ICellRenderer;
				}
				else
				{
					// out of renderers, create a new one:
					renderer=getDisplayObjectInstance(getStyleValue("cellRenderer")) as ICellRenderer;
					var rendererSprite:Sprite=renderer as Sprite;
					if (rendererSprite != null)
					{
						rendererSprite.addEventListener(MouseEvent.CLICK, handleCellRendererClick, false, 0, true);
						rendererSprite.addEventListener(MouseEvent.ROLL_OVER, handleCellRendererMouseEvent, false, 0, true);
						rendererSprite.addEventListener(MouseEvent.ROLL_OUT, handleCellRendererMouseEvent, false, 0, true);
						rendererSprite.addEventListener(Event.CHANGE, handleCellRendererChange, false, 0, true);
						rendererSprite.doubleClickEnabled=true;
						rendererSprite.addEventListener(MouseEvent.DOUBLE_CLICK, handleCellRendererDoubleClick, false, 0, true);

						if (rendererSprite["setStyle"] != null)
						{
							for (var n:String in rendererStyles)
							{
								rendererSprite["setStyle"](n, rendererStyles[n])
							}
						}
					}
				}
				list.addChild(renderer as Sprite);
				activeCellRenderers.push(renderer);

				renderer.y=rowH * row;
				renderer.x=colW * col;
				renderer.setSize(columnWidth, rowHeight);

				var label:String=itemToLabel(item);

				var icon:Object=null;
				if (_iconFunction != null)
				{
					icon=_iconFunction(item);
				}
				else if (_iconField != null)
				{
					icon=item[_iconField];
				}

				var source:Object=null;
				if (_sourceFunction != null)
				{
					source=_sourceFunction(item);
				}
				else if (_sourceField != null)
				{
					source=item[_sourceField];
				}

				if (!reused)
				{
					renderer.data=item;
				}

				renderer.listData=new TileListData(label, icon, source, this, itemIndex, baseRow + row, baseCol + col) as ListData;

				renderer.selected=(_selectedIndices.indexOf(itemIndex) != -1);

				// force an immediate draw (because render event will not be called on the renderer):
				if (renderer is UIComponent)
				{
					var rendererUIC:UIComponent=renderer as UIComponent;
					rendererUIC.drawNow();
				}
				i++;
			}
		}


		/**
		 * @private (protected)
		 */
		override protected function keyDownHandler(event:KeyboardEvent):void
		{
			event.stopPropagation();
			if (!selectable)
			{
				return;
			}
			switch (event.keyCode)
			{
				case Keyboard.UP:
				case Keyboard.DOWN:
					moveSelectionVertically(event.keyCode, event.shiftKey && _allowMultipleSelection, event.ctrlKey && _allowMultipleSelection);
					break;
				case Keyboard.PAGE_UP:
				case Keyboard.PAGE_DOWN:
				case Keyboard.END:
				case Keyboard.HOME:
					if (_scrollDirection == ScrollBarDirection.HORIZONTAL)
					{
						moveSelectionHorizontally(event.keyCode, event.shiftKey && _allowMultipleSelection, event.ctrlKey && _allowMultipleSelection);
					}
					else
					{
						moveSelectionVertically(event.keyCode, event.shiftKey && _allowMultipleSelection, event.ctrlKey && _allowMultipleSelection);
					}
					break;
				case Keyboard.LEFT:
				case Keyboard.RIGHT:
					moveSelectionHorizontally(event.keyCode, event.shiftKey && _allowMultipleSelection, event.ctrlKey && _allowMultipleSelection);
					break;
				default:
					var nextIndex:int=getNextIndexAtLetter(String.fromCharCode(event.keyCode), selectedIndex);
					if (nextIndex > -1)
					{
						selectedIndex=nextIndex;
						scrollToSelected();
					}
					break;
			}
		}

		/**
		 * @private (protected)
		 */
		protected function calculateAvailableHeight():Number
		{
			var pad:Number=Number(getStyleValue("contentPadding"));
			return height - pad * 2 - ((_horizontalScrollPolicy == ScrollPolicy.ON || (_horizontalScrollPolicy == ScrollPolicy.AUTO && _maxHorizontalScrollPosition > 0)) ? 15 : 0);
		}


		/**
		 * @private (protected)
		 * Moves the selection in a vertical direction in response
		 * to the user selecting items using the up-arrow or down-arrow
		 * Keys and modifiers such as the Shift and Ctrl keys.
		 *
		 * @param code The key that was pressed (e.g. Keyboard.DOWN)
		 *
		 * @param shiftKey <code>true</code> if the shift key was held down when
		 *        the keyboard key was pressed.
		 *
		 * @param ctrlKey <code>true</code> if the ctrl key was held down when
		 *        the keyboard key was pressed
		 */
		override protected function moveSelectionVertically(code:uint, shiftKey:Boolean, ctrlKey:Boolean):void
		{
			var totalRows:uint=Math.max(1, (Math.max(contentHeight, availableHeight) / _rowHeight << 0));
			var perRow:uint=Math.ceil(Math.max(columnCount * rowCount, length) / totalRows); // This is wrong.
			var totalContentRows:uint=Math.ceil(length / perRow);

			var index:int;
			var newIndex:int;
			switch (code)
			{
				case Keyboard.UP:
					index=selectedIndex - perRow;
					break;
				case Keyboard.DOWN:
					index=selectedIndex + perRow;
					break;
				case Keyboard.HOME:
					index=0;
					break;
				case Keyboard.END:
					index=length - 1;
					break;
				case Keyboard.PAGE_DOWN:
					newIndex=selectedIndex + perRow * (totalContentRows - 1);
					if (newIndex >= length)
					{
						newIndex-=perRow;
					}
					index=Math.min(length - 1, newIndex);
					break;
				case Keyboard.PAGE_UP:
					newIndex=selectedIndex - perRow * (totalContentRows - 1);
					if (newIndex < 0)
					{
						newIndex+=perRow;
					}
					index=Math.max(0, newIndex);
					break;
			}

			doKeySelection(index, shiftKey, ctrlKey);
			scrollToSelected();
		}

		/**
		 * @private (protected)
		 * Moves the selection in a horizontal direction in response
		 * to the user selecting items using the left-arrow or right-arrow
		 * keys and modifiers such as  the Shift and Ctrl keys.
		 *
		 * <p>Not implemented in List because the default list
		 * is single column and does not scroll horizontally.</p>
		 *
		 * @param code The key that was pressed (e.g. Keyboard.LEFT)
		 *
		 * @param shiftKey <code>true</code> if the shift key was held down when
		 *        the keyboard key was pressed.
		 *
		 * @param ctrlKey <code>true</code> if the ctrl key was held down when
		 *        the keyboard key was pressed
		 */
		override protected function moveSelectionHorizontally(code:uint, shiftKey:Boolean, ctrlKey:Boolean):void
		{
			var totalCols:uint=Math.ceil(Math.max(rowCount * columnCount, length) / rowCount);

			var index:int;
			switch (code)
			{
				case Keyboard.LEFT:
					index=Math.max(0, selectedIndex - 1);
					break;
				case Keyboard.RIGHT:
					index=Math.min(length - 1, selectedIndex + 1);
					break;
				case Keyboard.HOME:
					index=0;
					break;
				case Keyboard.END:
					index=length - 1;
					break;
				case Keyboard.PAGE_UP:
					var firstIndex:int=selectedIndex - selectedIndex % totalCols;
					index=Math.max(0, Math.max(firstIndex, selectedIndex - columnCount));
					break;
				case Keyboard.PAGE_DOWN:
					var lastIndex:int=selectedIndex - selectedIndex % totalCols + totalCols - 1;
					index=Math.min(length - 1, Math.min(lastIndex, selectedIndex + totalCols));
					break;
			}

			doKeySelection(index, shiftKey, ctrlKey);
			scrollToSelected();
		}

		/**
		 * @private (protected)
		 * Changes the selected index, or adds or subtracts the index and
		 * all indices between when the shift key is used.
		 */
		protected function doKeySelection(newCaretIndex:uint, shiftKey:Boolean, ctrlKey:Boolean):void
		{
			var indices:Array=selectedIndices;
			var selChanged:Boolean=false;

			if (newCaretIndex < 0 || newCaretIndex > length - 1)
			{
				// The index is out of range, do nothing.
			}
			else if (shiftKey && indices.length > 0 && newCaretIndex != indices[0])
			{
				var firstIndex:uint=indices[0];
				indices=[];
				var i:int;
				if (newCaretIndex < firstIndex)
				{
					for (i=firstIndex; i >= newCaretIndex; i--)
					{
						indices.push(i);
					}
				}
				else
				{
					for (i=firstIndex; i <= newCaretIndex; i++)
					{
						indices.push(i);
					}
				}
				selChanged=true;
			}
			else
			{
				indices=[newCaretIndex];
				caretIndex=newCaretIndex;
				selChanged=true;
			}
			selectedIndices=indices;


			if (selChanged)
			{
				dispatchEvent(new Event(Event.CHANGE));
			}
			invalidate(InvalidationType.DATA);
		}


		/**
		 * @private (protected)
		 */
		override protected function initializeAccessibility():void
		{
			if (TileList.createAccessibilityImplementation != null)
			{
				TileList.createAccessibilityImplementation(this);
			}
		}
	}
}
